﻿using System;
using System.Windows;
using System.Windows.Media;
using Red.Core;

namespace Red.Controls.Diagrams.ViewModels
{
    /// <summary>
    /// Defines a connection between two connectors (aka connection points) of two nodes.
    /// </summary>  
    public sealed class ConnectionViewModel : ViewModel
    {
        #region Data

        /// <summary>
        /// The source connector the connection is attached to.
        /// </summary>
        private ConnectorViewModel _sourceConnector;

        /// <summary>
        /// The destination connector the connection is attached to.
        /// </summary>
        private ConnectorViewModel _destConnector;

        /// <summary>
        /// The source and dest hotspots used for generating connection points.
        /// </summary>
        private Point _sourceConnectorHotspot;
        private Point _destConnectorHotspot;

        /// <summary>
        /// Points that make up the connection.
        /// </summary>
        private PointCollection _points;

        /// <summary>
        /// Is connection active.
        /// </summary>
        private bool _isActive = true;

        /// <summary>
        /// Is connection valid. Connection is not valid if dragged on connectors with incompatible types of Binding property.
        /// </summary>
        private bool _isValid = true;

        /// <summary>
        /// Orientation which determines how connection is drawn.
        /// </summary>
        private NodeOrientation _orientation = NodeOrientation.Horizontal;

        #endregion Data

        #region Properties

        /// <summary>
        /// The source connector the connection is attached to.
        /// </summary>
        public ConnectorViewModel SourceConnector
        {
            get
            {
                return _sourceConnector;
            }
            set
            {
                if ( _sourceConnector == value )
                {
                    return;
                }

                if ( _sourceConnector != null )
                {
                    _sourceConnector.AttachedConnections.Remove( this );
                    _sourceConnector.HotspotUpdated -= sourceConnector_HotspotUpdated;
                }

                _sourceConnector = value;

                if ( _sourceConnector != null )
                {
                    _sourceConnector.AttachedConnections.Add( this );
                    _sourceConnector.HotspotUpdated += sourceConnector_HotspotUpdated;
                    SourceConnectorHotspot = _sourceConnector.Hotspot;
                }

                OnPropertyChanged();
                OnPropertyChanged( "Color" );
                OnConnectionChanged();
            }
        }

        /// <summary>
        /// The destination connector the connection is attached to.
        /// </summary>
        public ConnectorViewModel DestConnector
        {
            get
            {
                return _destConnector;
            }
            set
            {
                if ( _destConnector == value )
                {
                    return;
                }

                if ( _destConnector != null )
                {
                    _destConnector.AttachedConnections.Remove( this );
                    _destConnector.HotspotUpdated -= new EventHandler<EventArgs>( destConnector_HotspotUpdated );
                }

                _destConnector = value;

                if ( _destConnector != null )
                {
                    _destConnector.AttachedConnections.Add( this );
                    _destConnector.HotspotUpdated += new EventHandler<EventArgs>( destConnector_HotspotUpdated );
                    DestConnectorHotspot = _destConnector.Hotspot;
                }

                OnPropertyChanged();
                OnPropertyChanged( "Color" );
                OnConnectionChanged();
            }
        }

        /// <summary>
        /// The source and dest hotspots used for generating connection points.
        /// </summary>
        public Point SourceConnectorHotspot
        {
            get
            {
                return _sourceConnectorHotspot;
            }
            set
            {
                _sourceConnectorHotspot = value;

                ComputeConnectionPoints();
                OnPropertyChanged( "Color" );
                OnPropertyChanged();
            }
        }

        public Point DestConnectorHotspot
        {
            get
            {
                return _destConnectorHotspot;
            }
            set
            {
                _destConnectorHotspot = value;

                ComputeConnectionPoints();
                OnPropertyChanged( "Color" );
                OnPropertyChanged();
            }
        }

        /// <summary>
        /// Points that make up the connection.
        /// </summary>
        public PointCollection Points
        {
            get
            {
                return _points;
            }
            set
            {
                _points = value;

                OnPropertyChanged();
            }
        }

        /// <summary>
        /// Points that make up the connection.
        /// </summary>
        public bool IsActive
        {
            get
            {
                return _isActive;
            }
            set
            {
                _isActive = value;

                OnPropertyChanged();
            }
        }

        public bool IsValid
        {
            get 
            { 
                return _isValid; 
            }
            set
            {
                if ( _isValid == value )
                    return;

                _isValid = value;
                OnPropertyChanged();
            }
        }

        public NodeOrientation Orientation
        {
            get 
            {
                return _orientation; 
            }
            set
            {
                if ( _orientation == value )
                    return;

                _orientation = value;
                ComputeConnectionPoints();
                OnPropertyChanged();
            }
        }

        public DiagramViewModel DiagramViewModel { get; set; }

        public Brush Color
        {
            get
            {
                var startColor = _sourceConnector?.Background ?? Colors.White;
                var endColor = _destConnector?.Background ?? Colors.White;
                var brush = new LinearGradientBrush( startColor, endColor, _sourceConnectorHotspot, _destConnectorHotspot )
                {
                    MappingMode = BrushMappingMode.Absolute
                };
                brush.Freeze();
                return brush;
            }
        }

        #endregion Properties

        #region Events

        /// <summary>
        /// Event fired when the connection has changed.
        /// </summary>
        public event EventHandler<EventArgs> ConnectionChanged;

        #endregion Events

        #region Private methods

        /// <summary>
        /// Raises the 'ConnectionChanged' event.
        /// </summary>
        private void OnConnectionChanged()
        {
            ConnectionChanged?.Invoke( this, EventArgs.Empty );
        }

        /// <summary>
        /// Event raised when the hotspot of the source connector has been updated.
        /// </summary>
        private void sourceConnector_HotspotUpdated( object sender, EventArgs e )
        {
            SourceConnectorHotspot = SourceConnector.Hotspot;
        }

        /// <summary>
        /// Event raised when the hotspot of the dest connector has been updated.
        /// </summary>
        private void destConnector_HotspotUpdated( object sender, EventArgs e )
        {
            DestConnectorHotspot = DestConnector.Hotspot;
        }

        /// <summary>
        /// Rebuild connection points.
        /// </summary>
        private void ComputeConnectionPoints()
        {
            //
            // Points calculation from graphEditorLayout.cpp 
            // void CEdGraphEditor::DrawLink()
            //
            Point srcDir;
            Point destDir;

            switch ( Orientation )
            {
                case NodeOrientation.Horizontal:
                    srcDir = new Point( 1, 0 ); // inputs always on the left side of the horizontal node
                    destDir = new Point( -1, 0 ); // outputs always on the right side of the horizontal node
                    break;
                case NodeOrientation.Vertical:
                    srcDir = new Point( 0, 1 ); // outputs always on the bottom of the vertical node
                    destDir = new Point( 0, -1 ); // inputs always on the top of the vertical node
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            var dist = Math.Max( Math.Abs( SourceConnectorHotspot.X - DestConnectorHotspot.X ), Math.Abs( SourceConnectorHotspot.Y - DestConnectorHotspot.Y ) );
            var offset = 5.0f + Math.Min( dist / 8.0f, 50.0f );

            var computedPoints = new PointCollection 
            { 
                SourceConnectorHotspot,
                new Point( SourceConnectorHotspot.X + srcDir.X * offset, SourceConnectorHotspot.Y + srcDir.Y * offset ),
                new Point( DestConnectorHotspot.X + destDir.X * offset, DestConnectorHotspot.Y + destDir.Y * offset ),
                DestConnectorHotspot
            };

            computedPoints.Freeze();
            Points = computedPoints;
        }

        #endregion Private methods
    }
}
